package mylittleinvestigations.CaseInformation;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import mylittleinvestigations.EasingFunctions.EasingFunction;
import mylittleinvestigations.EasingFunctions.LinearStepEase;
import mylittleinvestigations.Utils;
import org.dawnmew.jpurewave.SoundSystem;
import org.newdawn.slick.GameContainer;

public class AudioManager
{
    private ArrayList<String> dialogFilePathList;
    private HashMap<String, ArrayList<String>> hoofstepSoundIdListByTexture;
    private ArrayList<String> currentHoofstepSoundIdList;
    private ArrayList<String> bgmIdsWantedList;
    private ArrayList<String> sfxIdsWantedList;
    
    private FadeAction fadeAction;
    private EasingFunction fadeEase;
    
    private float musicVolMaster = 0.75f;

    public AudioManager()
    {
        this.dialogFilePathList = new ArrayList<String>();
        this.hoofstepSoundIdListByTexture = new HashMap<String, ArrayList<String>>();
        this.bgmIdsWantedList = new ArrayList<String>();
        this.sfxIdsWantedList = new ArrayList<String>();
        
        SoundSystem.getInstance().init();
        SoundSystem.getInstance().setVolumeMusic(musicVolMaster);
    }
    
    public void RequestBgm(String bgmId)
    {
        if (!this.bgmIdsWantedList.contains(bgmId))
        {
            this.bgmIdsWantedList.add(bgmId);
        }
    }
    
    public void RequestSfx(String sfxId)
    {
        if (!this.sfxIdsWantedList.contains(sfxId))
        {
            this.sfxIdsWantedList.add(sfxId);
        }
    }

    public void PlayBgmWithId(String id)
    {
        if (Utils.isNullOrEmpty(id))
        {
            // If we've got no ID, that's a cue to stop the
            // currently playing music.
            SoundSystem.getInstance().stopMusic();
            return;
        }
        else if (id.equals(SoundSystem.getInstance().getPlayingMusic()))
        {
            // This song is already playing, so nothing to do.
            return;
        }

        SoundSystem.getInstance().setVolumeMusic(this.musicVolMaster);
        SoundSystem.getInstance().playMusic(id);
        this.fadeEase = null;
    }

    public void PauseCurrentBgm()
    {
        if (!SoundSystem.getInstance().getPlayingMusic().equals(""))
        {
            this.InitializeFadeEase(1000, true);
            this.fadeAction = FadeAction.Pause;
        }
    }

    public void ResumeCurrentBgm()
    {
        if (!SoundSystem.getInstance().getPlayingMusic().equals(""))
        {
            SoundSystem.getInstance().playMusic();
            this.InitializeFadeEase(1000, false);
            this.fadeAction = FadeAction.Resume;
        }
    }

    public void StopCurrentBgm()
    {
        if (!SoundSystem.getInstance().getPlayingMusic().equals(""))
        {
            this.InitializeFadeEase(1000, true);
            this.fadeAction = FadeAction.Stop;
        }
    }

    public void Update(GameContainer gc, int delta)
    {
        if (this.fadeEase != null)
        {
            this.fadeEase.Update(gc, delta);

            if (this.fadeEase.getIsFinished())
            {
                switch (this.fadeAction)
                {
                    case Pause:
                        SoundSystem.getInstance().pauseMusic();
                        break;

                    case Resume:
                        break;

                    case Stop:
                        SoundSystem.getInstance().stopMusic();
                        break;
                }
                
                this.fadeEase = null;
            }
            else
            {
                SoundSystem.getInstance().setVolumeMusic(this.musicVolMaster * (float)this.fadeEase.GetCurrentValue());
            }
        }
    }
    
    public void setMusicVolume(float vol)
    {
        this.musicVolMaster = vol;
        
        if (this.fadeEase != null)
        {
            SoundSystem.getInstance().setVolumeMusic(this.musicVolMaster * (float)this.fadeEase.GetCurrentValue());
        }
        else
        {
            SoundSystem.getInstance().setVolumeMusic(this.musicVolMaster);
        }
    }
    
    public void AddDialogFilePath(String filePath)
    {
        if (!this.dialogFilePathList.contains(filePath))
        {
            this.dialogFilePathList.add(filePath);
        }
    }
    
    public void BeginHoofstepTexture(String textureName)
    {
        this.currentHoofstepSoundIdList = new ArrayList<String>();
        this.hoofstepSoundIdListByTexture.put(textureName, this.currentHoofstepSoundIdList);
    }
    
    public void AddHoofstepSoundId(String id)
    {
        if (this.currentHoofstepSoundIdList != null)
        {
            this.currentHoofstepSoundIdList.add(id);
        }
    }
    
    public void EndHoofstepTexture()
    {
        this.currentHoofstepSoundIdList = null;
    }
    
    private void InitializeFadeEase(int msDuration, boolean fadeOut)
    {
        this.fadeEase = new LinearStepEase(fadeOut ? 1 : 0, fadeOut ? 0 : 1, msDuration, 10);
        this.fadeEase.Begin();
    }

    void SaveToXml(CaseXmlWriter writer) throws IOException
    {
        writer.StartElement("AudioManager");
        
        ArrayList<String> filenameList = new ArrayList<String>();
        ArrayList<String> missingFilenameList = new ArrayList<String>();
        
        Files.createDirectory(Paths.get(Case.getInstance().getFilePath(), "BGM"));
        Files.createDirectory(Paths.get(Case.getInstance().getFilePath(), "SFX"));
        Files.createDirectory(Paths.get(Case.getInstance().getFilePath(), "Dialog"));
        
        writer.StartElement("BgmIdToFilePathDictionary");
        String[] bgmIds = SoundSystem.getInstance().getAllMusicIDs().toArray(new String[0]);       
        
        for (int i = 0; i < bgmIds.length; i++)
        {
            String bgmId = bgmIds[i];
            String originalFilePath = SoundSystem.getInstance().getMusicFilepath(bgmId);
            String finalFilePath = Paths.get("BGM", Paths.get(originalFilePath).getFileName().toString()).toString().replaceAll("\\\\", "/");
            
            writer.StartElement("Entry");
            writer.WriteTextElement("BgmId", bgmId);
            writer.WriteTextElement("FilePath", finalFilePath);
            writer.EndElement();
            
            try
            {
                Files.copy(Paths.get("data/" + originalFilePath + "A.ogg"), Paths.get(Case.getInstance().getFilePath(), finalFilePath + "A.ogg"), REPLACE_EXISTING);
            }
            catch (NoSuchFileException ex)
            {
                missingFilenameList.add(ex.getMessage());
            }
            
            try
            {
                Files.copy(Paths.get("data/" + originalFilePath + "B.ogg"), Paths.get(Case.getInstance().getFilePath(), finalFilePath + "B.ogg"), REPLACE_EXISTING);
            }
            catch (NoSuchFileException ex)
            {
                missingFilenameList.add(ex.getMessage());
            }
        }
        
        writer.EndElement();
        
        for (String bgmId : this.bgmIdsWantedList)
        {
            filenameList.add(bgmId + "A.ogg");
            filenameList.add(bgmId + "B.ogg");
            
            if (!SoundSystem.getInstance().getAllMusicIDs().contains(bgmId))
            {
                missingFilenameList.add("data\\Dialog\\BGM ID: " + bgmId);
            }
        }
        
        writer.StartElement("SfxIdToFilePathDictionary");
        String[] sfxIds = SoundSystem.getInstance().getAllSoundIDs().toArray(new String[0]);
        
        for (int i = 0; i < sfxIds.length; i++)
        {
            String sfxId = sfxIds[i];
            String originalFilePath = SoundSystem.getInstance().getSoundFilepath(sfxId);
            String finalFilePath = Paths.get("SFX", Paths.get(SoundSystem.getInstance().getSoundFilepath(sfxId)).getFileName().toString()).toString().replaceAll("\\\\", "/");
            
            writer.StartElement("Entry");
            writer.WriteTextElement("SfxId", sfxId);
            writer.WriteTextElement("FilePath", finalFilePath);
            writer.EndElement();
            
            try
            {
                Files.copy(Paths.get("data/" + originalFilePath + ".ogg"), Paths.get(Case.getInstance().getFilePath(), finalFilePath + ".ogg"), REPLACE_EXISTING);
            }
            catch (NoSuchFileException ex)
            {
                missingFilenameList.add(ex.getMessage());
            }
        }
        
        writer.EndElement();
        
        for (String sfxId : this.sfxIdsWantedList)
        {
            filenameList.add(sfxId + ".ogg");
            
            if (!SoundSystem.getInstance().getAllSoundIDs().contains(sfxId))
            {
                missingFilenameList.add("data\\Dialog\\SFX ID: " + sfxId);
            }
        }
        
        // We don't need to save any XML for the dialog file paths,
        // since each ShowDialogAction instance holds the file path,
        // but we do still need to copy them into the case file.
        for (int i = 0; i < this.dialogFilePathList.size(); i++)
        {
            String originalFilePath = this.dialogFilePathList.get(i);
            String finalFilePath = Paths.get("Dialog", Paths.get(originalFilePath).getFileName().toString()).toString().replaceAll("\\\\", "/");
            
            try
            {
                Files.copy(Paths.get("data/" + originalFilePath + ".ogg"), Paths.get(Case.getInstance().getFilePath(), finalFilePath + ".ogg"), REPLACE_EXISTING);
                filenameList.add((originalFilePath + ".ogg").substring("Dialog/".length()));
            }
            catch (NoSuchFileException ex)
            {
                missingFilenameList.add(ex.getMessage());
            }
        }
        
        Collections.sort(missingFilenameList);
        Collections.sort(filenameList);
        
        if (!missingFilenameList.isEmpty())
        {
            String missingFilenames = "Missing filenames:" + System.lineSeparator() + System.lineSeparator();
            
            for (String missingFilename : missingFilenameList)
            {
                int lastDirectorySeparator = missingFilename.lastIndexOf("\\");
                missingFilenames += (lastDirectorySeparator >= 0 ? missingFilename.substring(missingFilename.lastIndexOf("\\") + 1) : missingFilename) + System.lineSeparator();
            }
            
            throw new NoSuchFileException(missingFilenames);
        }
        
        ArrayList<String> unusedFilenameList = new ArrayList<String>();
        
        for (File dialogFile : new File("data/Dialog/").listFiles())
        {
            if (dialogFile.isFile())
            {
                if (!filenameList.contains(dialogFile.getName()))
                {
                    unusedFilenameList.add(dialogFile.getName());
                }
            }
        }
        
        for (File sfxFile : new File("data/SFX/").listFiles())
        {
            if (sfxFile.isFile())
            {
                if (!filenameList.contains(sfxFile.getName()))
                {
                    unusedFilenameList.add(sfxFile.getName());
                }
            }
        }
        
        for (File bgmFile : new File("data/BGM/").listFiles())
        {
            if (bgmFile.isFile())
            {
                if (!filenameList.contains(bgmFile.getName()))
                {
                    unusedFilenameList.add(bgmFile.getName());
                }
            }
        }
        
        Collections.sort(unusedFilenameList);
        
        if (!unusedFilenameList.isEmpty())
        {
            String unusedFilenames = "Unused filenames:" + System.lineSeparator() + System.lineSeparator();
            
            for (String unusedFilename : unusedFilenameList)
            {
                unusedFilenames += unusedFilename + System.lineSeparator();
            }
            
            throw new NoSuchFileException(unusedFilenames);
        }
        
        writer.StartElement("HoofstepSoundIdListByTexture");
        String[] textureNames = this.hoofstepSoundIdListByTexture.keySet().toArray(new String[0]);
        
        for (int i = 0; i < textureNames.length; i++)
        {
            String textureName = textureNames[i];
            ArrayList<String> hoofstepSoundIdList = this.hoofstepSoundIdListByTexture.get(textureName);
            
            writer.StartElement("Entry");
            writer.WriteTextElement("TextureName", textureName);
            
            writer.StartElement("HoofstepSoundIdList");
            
            for (int j = 0; j < hoofstepSoundIdList.size(); j++)
            {
                writer.StartElement("Entry");
                writer.WriteTextElement("SoundId", hoofstepSoundIdList.get(j));
                writer.EndElement();
            }
        
            writer.EndElement();
            
            writer.EndElement();
        }
        
        writer.EndElement();
        
        writer.EndElement();
    }

    public AudioManager(CaseXmlReader reader)
    {
        SoundSystem.getInstance().init();
        
        reader.StartElement("AudioManager");
        
        reader.StartElement("BgmIdToFilePathDictionary");
        reader.StartList("Entry");
        
        while (reader.MoveToNextListItem())
        {
            String bgmId = reader.ReadTextElement("BgmId");
            String filePath = reader.ReadTextElement("FilePath");
            SoundSystem.getInstance().loadMusic(bgmId, filePath, false);
        }
        
        reader.EndElement();
        
        reader.StartElement("SfxIdToFilePathDictionary");
        reader.StartList("Entry");
        
        while (reader.MoveToNextListItem())
        {
            String sfxId = reader.ReadTextElement("SfxId");
            String filePath = reader.ReadTextElement("FilePath");
            SoundSystem.getInstance().loadSound(sfxId, filePath);
        }
        
        reader.EndElement();
        
        reader.EndElement();
    }

    private enum FadeAction
    {
        Pause,
        Resume,
        Stop
    }
}

